﻿// Copyright 2006 - Ricardo Stuven (rstuven@gmail.com)
//
// This file is part of MsSqlSpatial.
// MsSqlSpatial is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// MsSqlSpatial is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with MsSqlSpatial; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using GisSharpBlog.NetTopologySuite.Geometries;
using MsSqlSpatial;
//using GisSharpBlog.NetTopologySuite.CoordinateSystems;
//using GisSharpBlog.NetTopologySuite.CoordinateSystems.Transformations;
using System.Collections.Generic;
using GeoAPI.Geometries;
using SharpMap.CoordinateSystems.Transformations;
using SharpMap.CoordinateSystems;
using GisSharpBlog.NetTopologySuite.CoordinateSystems.Transformations;


public partial class UserDefinedFunctions
{
	private static readonly Dictionary<TransformCacheKey, IMathTransform> transformCache = new Dictionary<TransformCacheKey, IMathTransform>(new TransformCacheKey());
	private static readonly CoordinateSystemFactory coordinateSystemFactory = new CoordinateSystemFactory();
	private static readonly CoordinateTransformationFactory coordinateTransformationFactory = new CoordinateTransformationFactory();

	[SqlFunction(IsDeterministic = false, DataAccess = DataAccessKind.Read)]
	public static SqlBytes Transform(SqlBytes ewkb, SqlInt32 target_srid)
	{
		IGeometry geometry = ConvertGeometry.ToGeometry(ewkb);
		if (geometry != null)
		{
			int sourceSRID = geometry.SRID;
			int targetSRID = target_srid.Value;

			// Return the original geometry if SRIDs are equal
			if (sourceSRID == targetSRID)
				return ewkb;
			else
			{
				// Look up coordinate system transformation object in cache
				TransformCacheKey cacheKey = new TransformCacheKey(sourceSRID, targetSRID);
				IMathTransform mathTransform;
				if (!transformCache.TryGetValue(cacheKey, out mathTransform))
				{
					if (!SpatialRefSysTable.IsValidSRID(targetSRID))
						throw new InvalidSridException(targetSRID);

					// Create coordinate system transformation object and put it in cache
					string sourceSRWKT = SpatialRefSysTable.GetSRText(sourceSRID);
					string targetSRWKT = SpatialRefSysTable.GetSRText(targetSRID);
					ICoordinateSystem sourceCS = coordinateSystemFactory.CreateFromWkt(sourceSRWKT);
					ICoordinateSystem targetCS = coordinateSystemFactory.CreateFromWkt(targetSRWKT);
					ICoordinateTransformation transform = coordinateTransformationFactory.CreateFromCoordinateSystems(sourceCS, targetCS);
					mathTransform = transform.MathTransform;
					transformCache[cacheKey] = mathTransform;
				}
				// Transform source geometry's coordinate system
				IGeometry sourceGeometry = geometry;
				IGeometry targetGeometry = GeometryTransform.TransformGeometry(sourceGeometry, mathTransform);
				return ConvertGeometry.ToSqlBytes(targetGeometry, targetSRID);
			}
		}
		return SqlBytes.Null;
	}

	// This trigger is registered in SpatialRefSysTable::ExecuteCreate
	public static void RefreshTransformCacheTrigger()
	{
		SqlTriggerContext triggerContext = SqlContext.TriggerContext;
		using (SqlConnection connection = DatabaseHelper.ContextConnection)
		{
			connection.Open();
			using (SqlCommand command = connection.CreateCommand())
			{
				// Get SRID of updated SRTEXT or deleted record.
				int srid = -1;
				if (triggerContext.TriggerAction == TriggerAction.Update
					&& triggerContext.IsUpdatedColumn(3 /* SRTEXT */))
				{
					DatabaseHelper.BuildCommand(command, "SELECT srid FROM inserted");
					try
					{
						srid = (int)command.ExecuteScalar();
					}
					catch { }
				}
				else if (triggerContext.TriggerAction == TriggerAction.Delete)
				{
					DatabaseHelper.BuildCommand(command, "SELECT srid FROM deleted");
					try
					{
						srid = (int)command.ExecuteScalar();
					}
					catch { }
				}
#if DEBUG
				SqlContext.Pipe.Send(string.Format("Trigger on SRID: {0}", srid));
#endif
				if (srid != -1)
				{
					// Build list of cache key items containing SRID
					List<TransformCacheKey> removeKeys = new List<TransformCacheKey>();
					foreach (TransformCacheKey cacheKey in transformCache.Keys)
					{
						if (cacheKey.part1 == srid || cacheKey.part2 == srid)
						{
							removeKeys.Add(cacheKey);
						}
					}
					// Remove by found cache keys
					foreach (TransformCacheKey cacheKey in removeKeys)
					{
						transformCache.Remove(cacheKey);
#if DEBUG
						SqlContext.Pipe.Send(string.Format("{0} removed from transform cache.", cacheKey));
#endif
					}
				}
			}
		}

	}

#if DEBUG
	[SqlProcedure]
	public static void TransformCache_List()
	{
		SqlContext.Pipe.Send(string.Format("{0} items in cache.", transformCache.Count));
		foreach (KeyValuePair<TransformCacheKey, IMathTransform> item in transformCache)
			SqlContext.Pipe.Send(string.Format("\t{0}", item.Key));
	}

	[SqlProcedure]
	public static void TransformCache_Clear()
	{
		transformCache.Clear();
		SqlContext.Pipe.Send("Cache cleared.");
	}

	[SqlProcedure]
	public static void TransformCache_Get(int sourceSRID, int targetSRID)
	{
		TransformCacheKey key = new TransformCacheKey(sourceSRID, targetSRID);
		IMathTransform mathTransform;
		if (transformCache.TryGetValue(key, out mathTransform))
			SqlContext.Pipe.Send(string.Format("{0} exists in cache.", key));
		else
			SqlContext.Pipe.Send(string.Format("{0} doesn't exist in cache.", key));
	}

	[SqlProcedure]
	public static void TransformCache_Remove(int sourceSRID, int targetSRID)
	{
		TransformCacheKey key = new TransformCacheKey(sourceSRID, targetSRID);
		if (transformCache.Remove(key))
			SqlContext.Pipe.Send(string.Format("{0} removed from cache.", key));
		else
			SqlContext.Pipe.Send(string.Format("{0} doesn't exist in cache.", key));
	}
#endif

	private class TransformCacheKey : CompositeKey<int, int>, IEqualityComparer<TransformCacheKey>
	{
		public TransformCacheKey() : base() { }
		public TransformCacheKey(int sourceSRID, int targetSRID) : base(sourceSRID, targetSRID) { }

		#region IEqualityComparer Members

		public bool Equals(TransformCacheKey x, TransformCacheKey y)
		{
			return base.Equals(x, y);
		}

		public int GetHashCode(TransformCacheKey obj)
		{
			return base.GetHashCode(obj);
		}

		#endregion
	}

	private class CompositeKey<TPart1, TPart2> : IEqualityComparer<CompositeKey<TPart1, TPart2>>
	{
		public TPart1 part1;
		public TPart2 part2;

		public CompositeKey()
		{
		}

		public CompositeKey(TPart1 part1, TPart2 part2)
		{
			this.part1 = part1;
			this.part2 = part2;
		}

		#region IEqualityComparer Members

		public bool Equals(CompositeKey<TPart1, TPart2> x, CompositeKey<TPart1, TPart2> y)
		{
			return x.part1.Equals(y.part1) && x.part2.Equals(y.part2);
		}

		public int GetHashCode(CompositeKey<TPart1, TPart2> obj)
		{
			return obj.part1.GetHashCode() ^ obj.part2.GetHashCode();
		}

		#endregion

#if DEBUG
		public override string ToString()
		{
			return string.Format("({0}, {1})", this.part1, this.part2);
		}
#endif

	}


};

